from django.core.paginator import Paginator, Page
import settings
import md5 #TODO: Change to Hashlib

feeds = {}
feeds_secure = {}
feeds_cache = {}

    #Handle pagination with a generic list of items, can accept a prefix
    #page_params needs to support get('key', 'default')
def page_of(list, page_params, page=1, per_page=10):
    #TODO: rewrite below with more proper handling?
    try: page = int(page_params.get(settings.PAGE_PREFIX, page))
    except: pass
    try: per_page = int(page_params.get(settings.PER_PAGE_PREFIX, per_page))
    except: pass

    paginator = Paginator(list, per_page)
    try: result = paginator.page(page)
    except: result = paginator.page(paginator.num_pages)
    return result

def to_dict(object, *args, **kwargs):
    if hasattr(object, '__iter__'):
        return [getattr(item, settings.TO_DICT_ATTR)(*args, **kwargs) for item in object]
    else:
        return getattr(object, settings.TO_DICT_ATTR)(*args, **kwargs)

def merge(params, query, can_override=False):
    if can_override:
        for k in query: params[k] = query[k] 
    else:
        for k in query:
            if not k in params: params[k] = query[k] 
    return params

from django.core.cache import cache
#from django.utils import simplejson as json #Conflicts with Django's encoder later

@classmethod
def mega_feeder(type, params = None, request_params = None, prefix = '',
         defaults = None, can_override=False, **kwargs):
    """
    Returns a mega_feed with appropriate parameters set

    type           -- The calling class (@classmethod)
    params         -- A dictionary of values to be passed into filter()
    request_params -- A QueryDict containing (PREFIX_)key:value pairs
    prefix         -- The root element of the dictionary
    defaults       -- Default values to supplement either params
    can_override   -- Whether the default values can be overriden
    """
    if not prefix:
        prefix = type.__name__.lower()
    params = params or {}
    if request_params:
        for key in request_params:
            if ('%s_' % key).startswith(prefix):
                params[key[len(prefix)+1:]] = request_params[key]
    if defaults:
        params = merge(defaults, params, can_override=can_override)
    return mega_feed(type, params=params, prefix=prefix, **kwargs)

def mega_feed(type, params=None, prefix='', cache_time=0, user=None, **kwargs):
    """
    Returns a paginated dictionary of the calling type

    type           -- The class to serialize
    params         -- A dictionary of values to be passed into filter()
    prefix         -- The root element of the dictionary
    cache_time     -- If defined, this feed will be cached for this many seconds
    paginate       -- Whether to paginate (and include pagination data)
    """


    ############   Python Unicode Dict Hack   ##############
    d = {}
    if hasattr(params, '__iter__'):
	    for k in params:
	        d[str(k)] = params[k]
    params = d
    ############  /Python Unicode Dict Hack   ##############

    value = None
    if not prefix:
        prefix = type.__name__.lower()

    if not cache_time and type in feeds_cache:
        cache_time = feeds_cache[type]

    if cache_time:
        key = md5.new(''.join(['%s=%s' % (k, hasattr(params[k], 'id')
                 and str(params[k].id) or str(params[k])) for k in
                 sorted(params.keys())]) + type.__name__).hexdigest()
        value = cache.get(key)
    if not value:
        queryset = getattr(type, settings.FILTER_ATTR)(user=user, **params)
        set_page = settings.PAGINATE_ARG in params
        if set_page and params[settings.PAGINATE_ARG] or not set_page:
            queryset = page_of(queryset, params)
            value = {prefix: {'page_data': {
                'page_count': queryset.paginator.num_pages,
                'per_page': queryset.paginator.per_page,
                'object_count': queryset.paginator.count,
                'current_page': queryset.number}, 'filter_list': params,
                'object_list': to_dict(queryset.object_list, **params),
                'type': type.__name__.lower()}}
        else:
            if settings.PER_PAGE_PREFIX in params:
                per_page = params[settings.PER_PAGE_PREFIX]
            else: per_page = 100
            if settings.PAGE_PREFIX in params:
                page = params[settings.PAGE_PREFIX] or 1
            else: page = 1
            value = {prefix: {'page_data': {
                'per_page': per_page,
                'current_page': page}, 'filter_list': params,
                'object_list': to_dict(queryset[(page-1)*per_page:page*per_page], **params),
                'type': type.__name__.lower()}}
        if cache_time:
            cache.set(key, value, cache_time)
    return value

def xml_serialize(object):
    # Not sure about this line, requires additional imports
    # if isinstance(object, QuerySet) or isinstance(object, MultiDBQuerySet):
    #      object = to_dict(object.to_dict)

    if isinstance(object, dict):
        return ''.join(['<%(node)s>%(value)s</%(node)s>' % {'node': key,
                 'value': xml_serialize(object[key])} for key in object])
    elif isinstance(object, list):
        return ''.join([xml_serialize(i) for i in object])
    elif isinstance(object, tuple):
        return ''.join([xml_serialize(i) for i in object])
    elif isinstance(object, bool):
        return object and 1 or 0
    elif object == None:
        return ''
    elif isinstance(object, basestring):
        return '<![CDATA[%s]]>' % unicode(object)
    else: return unicode(object)


def serial_response(object):
    return {'response':object}

from django.db.models.fields.related import ForeignKey

def default_to_dict(self, *args, **kwargs):
    ret, expand, level = {}, [], 0

    if settings.EXPAND_RELATED in kwargs: expand = kwargs[settings.EXPAND_RELATED]
    for field in self._meta.fields:
        if isinstance(field, ForeignKey):
            ret[field.name] = {}
            for k in feeds:
                if feeds[k] == field.related.parent_model:
                    ret[field.name]['feed'] = k
                    if field.name in expand and hasattr(getattr(self,
                            field.name), settings.TO_DICT_ATTR):
                        if expand:
                            kwargs[settings.EXPAND_RELATED] = [e.split('.', 1)[1]
                                for e in expand if e.startswith(field.name + '.')]
                        ret[field.name].update(getattr(getattr(
                           self, field.name), settings.TO_DICT_ATTR)(**kwargs))
#                           **dict([(k.split('.', 1)[1], kwargs[k])
#                           for k in kwargs if '.' in k])))#if k.startswith(field.name+'.')])))
                    else:
                        ret[field.name]['id'] = getattr(self, field.name + '_id')
                    break
            if not ret[field.name]:
                ret[field.name]['id'] = getattr(self, field.name + '_id')
        else:
            field_value = getattr(self, field.name)
            name = '%s.%s' % (type(field_value).__module__, type(field_value).__name__)

            if name in settings.TO_DICT_FN:
                field_value = settings.TO_DICT_FN[name](field_value)
            ret[field.name] = field_value

    return {self.__class__.__name__.lower():ret}

@classmethod
def default_filter(cls, user=None, **kwargs):
    objects = cls.objects.all()
    try:
        options = cls._meta._name_map
    except AttributeError:
        options = cls._meta.init_name_map()
    except: options = [f.name for f in cls._meta.fields()]
    for key in kwargs:
        if key == settings.ORDERING:
            objects = objects.order_by(kwargs[key])
            continue
        if '.' in key:
            check = key.split('.')[0]
            field = key.replace('.', '__')
        else:
            check = field = key
        if check in options:
            if key.startswith('-') or key.startswith('_'):
                field = key[1:]
                objects = objects.exclude(**{field:kwargs[key]})
            else:
                objects = objects.filter(**{field:kwargs[key]})
    if cls in feeds_secure:
        objects = objects.filter(**{feeds_secure[cls]:user.id})
    return objects.distinct()

def register(model, name=None, secure=None, cache_time=0):
    if hasattr(model, '__iter__'):
        for i in model:
            if isinstance(i, dict): register(**i)
            else:                   register(i)
        return

    if name:
        name = name.lower()
    else:
        name = model.__name__.lower()
    if name in feeds:
        raise ValueError('%s is already registered.' % name)
    feeds[name] = model
    if not hasattr(model, settings.TO_DICT_ATTR):
        setattr(model, settings.TO_DICT_ATTR, default_to_dict)
    if not hasattr(model, settings.FILTER_ATTR):
        setattr(model, settings.FILTER_ATTR, default_filter)
    if not hasattr(model, settings.MEGA_FEED_ATTR):
        setattr(model, settings.MEGA_FEED_ATTR, mega_feeder)

    if secure:
        feeds_secure[model] = secure
    if cache:
        feeds_cache[model] = cache_time

def load_model(model):
    try: return feeds[model]
    except KeyError: return None
